﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using GerbilLib;
using GerbilMigrate;

namespace MediaGerbil
{
    /// <summary>
    /// This is the main window of MediaGerbil.
    /// </summary>
    public partial class Main : Form
    {
        /// <summary>
        /// Constant string for the MediaGerbil website.
        /// </summary>
        private readonly string MediaGerbilWebsite = "http://mediagerbil.codeplex.com/";

        private Preferences Preferences { get; set; }
        private AsyncBindingList<MediaItem> AllMediaItems { get; set; }
        private AsyncBindingList<MediaItem> CurrentMediaItems { get; set; }
        private AsyncBindingList<MediaItem> FilteredMediaItems { get; set; }
        private Dictionary<string, HashSet<IMediaItem>> MovieRatings { get; set; }
        private Dictionary<string, HashSet<IMediaItem>> SeriesRatings { get; set; }
        private Log log = new Log();
        private DiffMatchPatch.diff_match_patch dmp = new DiffMatchPatch.diff_match_patch();
        private string CurrentTitleEditingControlComboBoxText { get; set; }

        public Main()
        {
            InitializeComponent();
            Text = string.Format(Text, ProductVersion);
            // Create data grid columns and switch to default view
            PrepareDataGridView();
            showDefaultViewToolStripMenuItem_Click(null, null);
            // The following line is needed to force creation of the handle so it can be accessed
            // by threads other than the main thread
            IntPtr handle = log.Handle;
            Logger.LogMessageEvent += new EventHandler<LogEventArgs>(Logger_LogMessageEvent);
            TaskManager.TaskAddedEvent += new EventHandler<TaskEventArgs>(TaskManager_TaskAddedEvent);
            TaskManager.TaskRemovedEvent += new EventHandler<TaskEventArgs>(TaskManager_TaskRemovedEvent);
            RestoreEnvironment();
        }

        private void TaskManager_TaskRemovedEvent(object sender, TaskEventArgs e)
        {
            UpdateTaskManagerStatus(e, false);
        }

        private void TaskManager_TaskAddedEvent(object sender, TaskEventArgs e)
        {
            UpdateTaskManagerStatus(e, true);
        }

        private void UpdateTaskManagerStatus(TaskEventArgs e, bool taskAdded)
        {
            string taskText = "No Background Activity!";
            if (TaskManager.CurrentTasks.Count > 0)
                taskText = string.Format("{0} Running Background Task{1}!", TaskManager.CurrentTasks.Count, TaskManager.CurrentTasks.Count == 1 ? string.Empty : "s");
            InvokeMethod(() => tasksToolStripDropDownButton.Text = taskText);
            Logger.LogMessage(string.Format("Background Task {0}{1} {2}.", e.Task.Id, string.IsNullOrWhiteSpace(e.TaskName) ? string.Empty : string.Format(": {0}", e.TaskName), taskAdded ? "started" : "finished"));
        }

        /// <summary>
        /// Prepares Data Grid View for Media Items display
        /// </summary>
        private void PrepareDataGridView()
        {
            dataGridView.AutoGenerateColumns = false;
            dataGridView.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView_EditingControlShowing);
            dataGridView.CellFormatting += new DataGridViewCellFormattingEventHandler(dataGridView_CellFormatting);

            // Original Title
            DataGridViewColumn originalTitle = new DataGridViewTextBoxColumn();
            originalTitle.Name = "OriginalTitle";
            originalTitle.DataPropertyName = "OriginalTitle";
            originalTitle.HeaderText = "Original Title";
            originalTitle.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            originalTitle.ReadOnly = true;
            dataGridView.Columns.Add(originalTitle);

            // Title
            DataGridViewTextBoxColumn title = new DataGridViewTextBoxColumn();
            title.CellTemplate = new DataGridViewTitleCell();
            title.Name = "Title"; 
            title.DataPropertyName = "Title";
            title.HeaderText = "Title";
            title.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dataGridView.Columns.Add(title);

            // Type
            DataGridViewComboBoxColumn type = new DataGridViewComboBoxColumn();
            type.Name = "MediaType";
            type.DataPropertyName = "MediaType";
            type.DataSource = Enum.GetValues(typeof(MediaType));
            type.HeaderText = "Type";
            type.SortMode = DataGridViewColumnSortMode.Automatic;
            type.DisplayStyleForCurrentCellOnly = true;
            dataGridView.Columns.Add(type);

            // Year
            DataGridViewColumn year = new DataGridViewTextBoxColumn();
            year.Name = "Year";
            year.DataPropertyName = "Year";
            year.HeaderText = "Year";
            dataGridView.Columns.Add(year);
            
            // MPAA Rating
            DataGridViewComboBoxColumn contentRating = new DataGridViewComboBoxColumn();
            contentRating.Name = "ContentRating";
            contentRating.DataPropertyName = "ContentRating";
            contentRating.DataSource = Enum.GetValues(typeof(ContentRating));
            contentRating.HeaderText = "Content";
            contentRating.SortMode = DataGridViewColumnSortMode.Automatic;
            contentRating.DisplayStyleForCurrentCellOnly = true;
            dataGridView.Columns.Add(contentRating);
            
            // IMDb ID
            DataGridViewColumn imdbId = new DataGridViewTextBoxColumn();
            imdbId.Name = "ImdbId";
            imdbId.DataPropertyName = "ImdbId";
            imdbId.HeaderText = "IMDb ID";
            imdbId.Visible = false;
            dataGridView.Columns.Add(imdbId);

            // IMDb Rating
            DataGridViewColumn rating = new DataGridViewTextBoxColumn();
            rating.Name = "Rating";
            rating.DataPropertyName = "Rating";
            rating.HeaderText = "Rating";
            rating.DefaultCellStyle.Format = "F1";
            dataGridView.Columns.Add(rating);
            
            // IMDb Votes
            DataGridViewColumn votes = new DataGridViewTextBoxColumn();
            votes.Name = "Votes";
            votes.DataPropertyName = "Votes";
            votes.HeaderText = "Votes";
            votes.DefaultCellStyle.Format = "N0";
            dataGridView.Columns.Add(votes);

            // TMDb ID
            DataGridViewColumn tmdbId = new DataGridViewTextBoxColumn();
            tmdbId.Name = "TmdbId";
            tmdbId.DataPropertyName = "TmdbId";
            tmdbId.HeaderText = "TMDb ID";
            tmdbId.Visible = false;
            dataGridView.Columns.Add(tmdbId);

            // TVDb ID
            DataGridViewColumn tvdbId = new DataGridViewTextBoxColumn();
            tvdbId.Name = "TvdbId";
            tvdbId.DataPropertyName = "TvdbId";
            tvdbId.HeaderText = "TVDb ID";
            tvdbId.Visible = false;
            dataGridView.Columns.Add(tvdbId);

            // File Path
            DataGridViewColumn filePath = new DataGridViewTextBoxColumn();
            filePath.Name = "FilePath";
            filePath.DataPropertyName = "Path";
            filePath.HeaderText = "File Path";
            filePath.ReadOnly = true;
            filePath.Visible = false;
            dataGridView.Columns.Add(filePath);
        }

        void dataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            MediaItem item = dataGridView.Rows[e.RowIndex].DataBoundItem as MediaItem;
            if (item == null) return;

            if (e.ColumnIndex == 1) e.CellStyle.ForeColor = item.ForeColor;
        }

        void EditingControl_KeyUp(object sender, KeyEventArgs e)
        {
            if (
                e.KeyCode == Keys.Down || e.KeyCode == Keys.Up || e.KeyCode == Keys.Left || e.KeyCode == Keys.Right || e.KeyCode == Keys.Return ||
                e.KeyCode == Keys.ShiftKey || e.KeyCode == Keys.ControlKey || e.KeyCode == Keys.Alt
                )
            {
                return;
            }

            TitleEditingControl comboBox = sender as TitleEditingControl;
            if (comboBox != null)
            {
                // Don't do anything if the Text didn't change or we don't have anything to populate with
                if (comboBox.Text == CurrentTitleEditingControlComboBoxText || MovieRatings == null || MovieRatings.Count == 0) return;
                PopulateTitleEditingControlComboBox(comboBox);
            }
        }

        void dataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            TitleEditingControl comboBox = e.Control as TitleEditingControl;
            if (comboBox != null)
            {
                comboBox.KeyUp -= new KeyEventHandler(EditingControl_KeyUp);
                comboBox.KeyUp += new KeyEventHandler(EditingControl_KeyUp);

                PopulateTitleEditingControlComboBox(comboBox, false);
            }
        }

        private void PopulateTitleEditingControlComboBox(TitleEditingControl comboBox, bool autoDropDown = true)
        {
            if (MovieRatings == null || MovieRatings.Count == 0) return;
            Cursor.Current = Cursors.WaitCursor;
            CurrentTitleEditingControlComboBoxText = comboBox.Text;
            // Remember where the cursor is because the ComboBox changes that value
            int selectionStart = comboBox.SelectionStart;

            comboBox.BeginUpdate();
            comboBox.Items.Clear();

            // Populate list from current title matching source
            MediaItem currentItem = dataGridView.CurrentRow.DataBoundItem as MediaItem;
            List<IMediaItem> items = GetTitleMatchList(MediaItem.GetCleanTitle(comboBox.Text, Preferences.TitleCleaningOptions), (currentItem != null) ? currentItem.MediaType : MediaType.Unkown);
            foreach (IMediaItem m in items)
                comboBox.Items.Add(m.Title);

            // Open the DropDown automatically
            if (comboBox.Items.Count > 0 && !comboBox.DroppedDown && autoDropDown)
            {
                comboBox.DropDownHeight = 106;
                comboBox.DroppedDown = true;
                comboBox.Text = CurrentTitleEditingControlComboBoxText;
            }

            comboBox.SelectionStart = selectionStart;
            comboBox.EndUpdate();
            Cursor.Current = Cursors.Default;
        }

        void dataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            //throw new NotImplementedException();
        }

        void Logger_LogMessageEvent(object sender, LogEventArgs e)
        {
            if (e.LoggingLevel == LoggingLevel.Debug) return;

            Action a = new Action(() => toolStripStatusLabel.Text = e.Message);
            if (InvokeRequired && !(Disposing || IsDisposed))
            {
                BeginInvoke(new MethodInvoker(a));
            }
            else a();
        }

        private void preferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PreferencesDialog p = new PreferencesDialog(Preferences);
            p.ShowDialog(this);
        }

        private void showDebugWindowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Debug d = new Debug();
            d.ShowDialog(this);
        }

        private async void refreshMediaListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            toolStripProgressBar.Style = ProgressBarStyle.Marquee;

            if (AllMediaItems == null) AllMediaItems = new AsyncBindingList<MediaItem>();
            
            Task first = TaskManager.Run("Remove Media Items With Unreachable Path", () => MediaCrawler.RemoveMediaItemsWithUnreachablePath(AllMediaItems));
            await first;
            Task second = TaskManager.Run("Remove Media Items With Excluded Path", () => MediaCrawler.RemoveMediaItemsWithExcludedPath(AllMediaItems, Preferences.ExcludedMediaDirectories));
            await second;
            Task third = TaskManager.Run("Remove Media Items In Ignored Directory", () => MediaCrawler.RemoveMediaItemsInIgnoredDirectory(AllMediaItems, Preferences.IgnoredDirectories));
            await third;
            Task fourth = TaskManager.Run("Remove Media Items Without Media Files", () => MediaCrawler.RemoveMediaItemsWithoutMediaFiles(AllMediaItems, Preferences.MediaFileExtensions));
            await fourth;
            //await TaskManager.WhenAll(first, second, third);
            //await TaskManager.Run("Update Media Items From Path", () => MediaCrawler.GetMediaItemsFromPath(Preferences.MediaDirectories, Preferences.ExcludedMediaDirectories, AllMediaItems, Preferences.MediaFileExtensions));
            MediaCrawler.GetMediaItemsFromPath(Preferences.MediaDirectories, Preferences.ExcludedMediaDirectories, AllMediaItems, Preferences.MediaFileExtensions, Preferences.IgnoredDirectories);
            // TODO: Following line needed?
            UpdateMediaGrid();

            CurrentMediaItems = AllMediaItems;
            dataGridView.DataSource = CurrentMediaItems;
        }

        private void UpdateMediaGrid()
        {
            //dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dataGridView.Refresh();
            ResetProgressBar();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void aboutMediaGerbilToolStripMenuItem_Click(object sender, EventArgs e)
        {
            About about = new About();
            about.ShowDialog(this);
        }

        private void statusStrip_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            log.Show();
            log.Focus();
        }

        private void Main_FormClosing(object sender, FormClosingEventArgs e)
        {
            saveMediaListToolStripMenuItem_Click(sender, e);
            Logger.LogMessage("Exiting... Thanks for using MediaGerbil!");
        }

        private void RestoreEnvironment()
        {
#if(DEBUG)
            showDebugWindowToolStripMenuItem.Enabled = true;
#endif
            // Load Preferences
            Preferences = PreferencesHelper.LoadPreferences() ?? new Preferences();
            if (Preferences.DeserializedWithErrors)
                if (DialogResult.Yes == MessageBox.Show(this, "Some of the Preferences could not be loaded, possibly because of a MediaGerbil update. Those Preferences have been set to their default values. Would you like to open the Preferences now?", "Preferences have been changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1))
                    preferencesToolStripMenuItem_Click(null, null);
            if (Preferences.UpgradedSchemaVersion)
                if (DialogResult.Yes == MessageBox.Show(this, "The Preferences were automatically updated, and some values might have been set to their default values. Would you like to open the Preferences now?", "Preferences have been changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1))
                    preferencesToolStripMenuItem_Click(null, null);

            // Load Media Items
            AllMediaItems = MediaItemsCache.LoadMediaItemsCache() ?? new AsyncBindingList<MediaItem>();
            CurrentMediaItems = AllMediaItems;
            dataGridView.DataSource = CurrentMediaItems;
        }

        private void InvokeMethod(Action a)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new MethodInvoker(a));
            }
            else
            {
                a();
            }
        }

        private void ResetProgressBar()
        {
            toolStripProgressBar.Style = ProgressBarStyle.Continuous;
        }

        private void updateIMDBMovieListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FileDownload download = null;
            switch (Preferences.TitleMatchingOptions.TitleMatchSource)
            {
                case TitleMatchingOptions.TitleMatchSources.MpaaRatings:
                    download = new FileDownload("ftp://ftp.fu-berlin.de/misc/movies/database/mpaa-ratings-reasons.list.gz");
                    break;
                case TitleMatchingOptions.TitleMatchSources.ImdbRatings:
                    download = new FileDownload("ftp://ftp.fu-berlin.de/misc/movies/database/ratings.list.gz");
                    break;
                case TitleMatchingOptions.TitleMatchSources.FullMovieList:
                    download = new FileDownload("ftp://ftp.fu-berlin.de/misc/movies/database/movies.list.gz");
                    break;
            }
            
            download.Show(this);
            download.Download();
        }

        void ProgressEvent(object sender, ProgressEventArgs e)
        {
            InvokeMethod(() => toolStripProgressBar.Value = (int)e.Percentage);
        }

        private void LoadMpaaRatings()
        {
            if (MovieRatings != null && MovieRatings.Count > 0)
            {
                Dictionary<string, HashSet<IMediaItem>>.ValueCollection.Enumerator hashEnumerator = MovieRatings.Values.GetEnumerator();
                hashEnumerator.MoveNext();
                HashSet<IMediaItem>.Enumerator itemEnumerator = hashEnumerator.Current.GetEnumerator();
                itemEnumerator.MoveNext();
                if (!(itemEnumerator.Current is MpaaRating)) MovieRatings.Clear();
            }
            if (MovieRatings == null || MovieRatings.Count == 0)
                MovieRatings = MpaaRating.LoadMpaaRatings();
        }

        private void LoadImdbRatings()
        {
            ClearImdbRatings(MovieRatings);
            ClearImdbRatings(SeriesRatings);
            if (MovieRatings == null || MovieRatings.Count == 0)
                MovieRatings = ImdbRating.LoadImdbMovieRatings();
            if (SeriesRatings == null || SeriesRatings.Count == 0)
                SeriesRatings = ImdbRating.LoadImdbSeriesRatings();
        }

        private void ClearImdbRatings(Dictionary<string, HashSet<IMediaItem>> ratings)
        {
            if (ratings != null && ratings.Count > 0)
            {
                Dictionary<string, HashSet<IMediaItem>>.ValueCollection.Enumerator hashEnumerator = ratings.Values.GetEnumerator();
                hashEnumerator.MoveNext();
                HashSet<IMediaItem>.Enumerator itemEnumerator = hashEnumerator.Current.GetEnumerator();
                itemEnumerator.MoveNext();
                if (!(itemEnumerator.Current is ImdbRating)) ratings.Clear();
            }
        }

        private void MatchTitlesFromImdbList(bool onlyMissingImdbItems = false, bool onlySelectedItems = false)
        {
            int matchesFound = 0;
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            ProgressEvent(null, new ProgressEventArgs(0));

            // Get match threshhold value from preferences
            dmp.Match_Threshold = Preferences.DiffMatchPatchMatchThreshhold;

            IList<MediaItem> items = null;
            if (onlySelectedItems) items = GetSelectedMediaItems();
            else if (onlyMissingImdbItems) items = GetMissingImdbItems();
            else items = CurrentMediaItems;

            if (items == null || items.Count == 0)
            {
                Logger.LogMessage("Not matching any titles since there are no items in the list!");
                return;
            }

            int count = items.Count;
            int itemsProcessed = 0;
            int percentage = 0;
            Logger.LogMessage(string.Format("Starting title matching on media list with {0} items.", count), LoggingLevel.Info);

            Parallel.ForEach(items, item =>
            {
                if (Preferences.TitleMatchingOptions.TitleSortOption == TitleMatchingOptions.TitleSortOptions.StopAtFirstMatch)
                {
                    if (MatchTitleFromImdbList(item)) matchesFound++;
                }
                else
                {
                    List<IMediaItem> results = GetAllTitleMatchesFromImdbList(item, (item.MediaType == MediaType.Movie) ? MovieRatings : SeriesRatings);
                    if (results.Count > 0)
                    {
                        // Since we sorted our matches we'll grab the first one
                        item.Title = results[0].Title;
                        matchesFound++;
                    }
                }
                int newPercentage = itemsProcessed * 100 / count;
                if (newPercentage != percentage)
                {
                    percentage = newPercentage;
                    ProgressEvent(null, new ProgressEventArgs(percentage));
                }
                itemsProcessed++;
            });

            ProgressEvent(null, new ProgressEventArgs(100));
            stopwatch.Stop();
            Logger.LogMessage(string.Format("Title matching completed in {0:F} seconds, {1} matches found.", stopwatch.ElapsedMilliseconds / 1000.0, matchesFound), LoggingLevel.Info);
        }

        /// <summary>
        /// TODO: Write improved method that uses keys instead of going through all the hashsets.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private bool MatchTitleFromImdbList(MediaItem item)
        {
            string cleanTitle = item.GetCleanTitle(Preferences.TitleCleaningOptions);
            foreach (HashSet<IMediaItem> h in MovieRatings.Values)
                foreach (IMediaItem m in h)
                {
                    int result = dmp.match_main(m.Title.ToLower(), cleanTitle, 0);
                    if (result != -1)
                    {
                        item.Title = m.Title;
                        if (m is MpaaRating) item.ContentRating = ((MpaaRating)m).Rating;
                        return true;
                    }
                }

            return false;
        }

        private List<IMediaItem> GetAllTitleMatchesFromImdbList(MediaItem item, Dictionary<string, HashSet<IMediaItem>> ratings)
        {
            int yearInPath = 0;
            if (Preferences.TitleMatchingOptions.UseYearInPath)
                if (Preferences.TitleMatchingOptions.UseYearInParentOnly)
                    int.TryParse(MediaItem.RegexLastYear.Match(Path.GetFileName(item.Path)).Value, out yearInPath);
                else
                    int.TryParse(MediaItem.RegexLastYear.Match(item.Path).Value, out yearInPath);

            return GetAllTitleMatchesFromImdbList(item.GetCleanTitle(Preferences.TitleCleaningOptions), ratings, yearInPath);
        }

        /// <summary>
        /// Improved method that uses keys instead of going through all the hashsets.
        /// </summary>
        /// <param name="itemTitle"></param>
        /// <returns></returns>
        private List<IMediaItem> GetAllTitleMatchesFromImdbList(string itemTitle, Dictionary<string, HashSet<IMediaItem>> ratings, int yearInPath = 0)
        {
            dmp.Match_Threshold = Preferences.DiffMatchPatchMatchThreshhold;
            List<IMediaItem> results = new List<IMediaItem>();
            if (ratings == null) return results;
            object lockObject = new object();

            Parallel.ForEach<string>(ratings.Keys, title =>
            //foreach(string title in ratings.Keys)
            {
                int result = dmp.match_main(title, itemTitle, 0);
                if (result != -1)
                {
                    foreach (IMediaItem m in ratings[title])
                    {
                        if (Preferences.TitleMatchingOptions.TitleSortOption == TitleMatchingOptions.TitleSortOptions.SortByPosition)
                            m.SortWeight = result;
                        else if (Preferences.TitleMatchingOptions.TitleSortOption == TitleMatchingOptions.TitleSortOptions.SortByLevenshtein)
                        {
                            List<DiffMatchPatch.Diff> diffs = dmp.diff_main(itemTitle, MediaItem.GetCleanTitle(m.Title, Preferences.TitleCleaningOptions));
                            m.SortWeight = dmp.diff_levenshtein(diffs);
                        }
                        else
                            m.SortWeight = 0;

                        // Speed up title matching for short strings
                        if (itemTitle.Length < 3 && m.SortWeight > 5) continue;

                        // Needed to make sure the parallel loops are done adding items before executing the sort
                        lock (lockObject)
                        {
                            results.Add(m);
                        }
                    }
                }
            });

            results.Sort(new IMediaItemSortWeightComparer(Preferences.TitleMatchingOptions.SortByNewerYear, yearInPath));
            return results;
        }

        private void MatchTitlesFromImdbQuery(bool onlyMissingImdbItems = false, bool onlySelectedItems = false)
        {
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            ProgressEvent(null, new ProgressEventArgs(0));

            IList<MediaItem> items = null;
            if (onlySelectedItems) items = GetSelectedMediaItems();
            else if (onlyMissingImdbItems) items = GetMissingImdbItems();
            else items = CurrentMediaItems;

            int count = items.Count;
            int itemProcessed = 0, matchesFound = 0, percentage = 0;
            Logger.LogMessage(string.Format("Starting title matching on media list with {0} items.", count));

            Parallel.ForEach(items, item =>
            {
                if (MatchTitleFromImdbQuery(item)) matchesFound++;
                int newPercentage = itemProcessed * 100 / count;
                if (newPercentage != percentage)
                {
                    percentage = newPercentage;
                    ProgressEvent(null, new ProgressEventArgs(percentage));
                }
                itemProcessed++;
            });

            ProgressEvent(null, new ProgressEventArgs(100));
            stopwatch.Stop();
            Logger.LogMessage(string.Format("Title matching completed in {0:F} seconds, {1} matches found.", stopwatch.ElapsedMilliseconds / 1000.0, matchesFound));
        }

        private bool MatchTitleFromImdbQuery(MediaItem item)
        {
            List<IMediaItem> results = ImdbScraper.GetImdbAdvancedTitleSearchMatches(item.Title, item.MediaType, Preferences.TitleCleaningOptions);
            if (results != null && results.Count > 0) // Only use the first result for now
            {
                IMediaItem result = results[0];
                item.Title = result.Title;
                if ( result is MediaItem ) item.ImdbId = ((MediaItem)result).ImdbId;
                return true;
            }
            return false;
        }

        private void GetImdbMediaInfo(List<MediaItem> items)
        {
            int processedIndices = 0;
            int percentage = 0;

            ProgressEvent(null, new ProgressEventArgs(0));
            foreach (MediaItem m in items)
            {
                ImdbScraper.GetImdbMediaInfo(m, updateVotesAndRatingOnly: getMediaInfoUpdateVotesAndRatingOnlyToolStripMenuItem.Checked);
                if (m == dataGridView.CurrentRow.DataBoundItem as MediaItem) UpdateMediaItemDetailsView(m);
                InvokeMethod(() => UpdateMediaGrid());
                int newPercentage = processedIndices * 100 / items.Count;
                if (newPercentage != percentage)
                {
                    percentage = newPercentage;
                    ProgressEvent(null, new ProgressEventArgs(percentage));
                }
                processedIndices++;
            }
            ProgressEvent(null, new ProgressEventArgs(100));
        }

        private void clearMediaListCacheToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (DialogResult.Yes == MessageBox.Show(this, "Do you really want to clear out your Media List cache? You will need to Refresh the Media List to rebuild the cache from scratch!", "Really clear media list cache?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2))
            {
                AllMediaItems.Clear();
                UpdateMediaGrid();
                UpdateMediaItemDetailsView();
            }
        }

        private void SaveCoverImageToFolder(List<MediaItem> items, CoverImageOptions coverImageOptions)
        {
            int processedIndices = 0, percentage = 0;
            int succeededItems = 0, failedItems = 0;

            ProgressEvent(null, new ProgressEventArgs(0));
            foreach (MediaItem m in items)
            {
                if (!string.IsNullOrEmpty(m.ImageBaseUrl))
                {
                    Logger.LogMessage(string.Format("Downloading Cover Image for title {0}", m.Title));
                    switch (coverImageOptions.CoverImageSource)
                    {
                        case CoverImageOptions.CoverImageSources.IMDb:
                            if (ImdbScraper.SaveCoverImageToFolder(m)) succeededItems++;
                            else failedItems++;
                            break;
                        case CoverImageOptions.CoverImageSources.TMDb:
                            if (TmdbScraper.SaveCoverImageToFolder(m, ConfigurationManager.AppSettings["TmdbApiKey"])) succeededItems++;
                            else failedItems++;
                            break;
                    }
                }
                int newPercentage = processedIndices * 100 / items.Count;
                if (newPercentage != percentage)
                {
                    percentage = newPercentage;
                    ProgressEvent(null, new ProgressEventArgs(percentage));
                }
                processedIndices++;
            }
            ProgressEvent(null, new ProgressEventArgs(100));
            Logger.LogMessage(string.Format("Successfully downloaded {0} Cover Images! ({1} failures)", succeededItems, failedItems));
        }

        private void dataGridView_RowEnter(object sender, DataGridViewCellEventArgs e)
        {
            MediaItem item = dataGridView.Rows[e.RowIndex].DataBoundItem as MediaItem;
            if (item != null) UpdateMediaItemDetailsView(item);
        }

        private void UpdateMediaItemDetailsView(MediaItem item = null, bool useSelectedMediaItem = false)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new MethodInvoker(() => UpdateMediaItemDetailsView(item, useSelectedMediaItem)));
            }
            else
            {
                // If no item was passed in get the currently selected one
                if (item == null && useSelectedMediaItem)
                    item = dataGridView.CurrentRow.DataBoundItem as MediaItem;

                if (item != null)
                {
                    lblTitle.Text = item.Title;
                    if (item.Rating != 0)
                        lblImdbRating.Text = string.Format("IMDb Rating {0:F1}/10 with {1:N0} votes", item.Rating, item.Votes);
                    else
                        lblImdbRating.Text = "IMDb Rating info not available";
                    SetImdbRating(item.Rating);
                    lblGenre.Text = string.Format("Genre(s): {0}", item.Genre);
                    lblDescription.Text = item.Description;
                    lblRuntime.Text = string.Format("{0} mins", item.Runtime);
                    SetCoverAndRatingImages(item);
                }
                else
                {
                    lblTitle.Text = "Select a title from the list to display details here!";
                    lblImdbRating.Text = string.Empty;
                    SetImdbRating(0);
                    lblGenre.Text = string.Empty;
                    lblDescription.Text = string.Empty;
                    lblRuntime.Text = string.Empty;
                    coverImagePictureBox.Image = null;
                }
            }
        }

        private void SetImdbRating(Single rating)
        {
            imdbRatingStarsPictureBox.Width = (int)(240 * rating / 10);
        }

        private void SetCoverAndRatingImages(MediaItem item)
        {
            // If an image is loading already cancel out of it
            coverImagePictureBox.CancelAsync();

            switch (item.ContentRating)
            {
                // MPAA Movie Ratings
                case ContentRating.G:
                    mpaaRatingPictureBox.Image = Properties.Resources.MPAA_G;
                    break;
                case ContentRating.NC17:
                    mpaaRatingPictureBox.Image = Properties.Resources.MPAA_NC17;
                    break;
                case ContentRating.PG:
                    mpaaRatingPictureBox.Image = Properties.Resources.MPAA_PG;
                    break;
                case ContentRating.PG13:
                    mpaaRatingPictureBox.Image = Properties.Resources.MPAA_PG13;
                    break;
                case ContentRating.R:
                    mpaaRatingPictureBox.Image = Properties.Resources.MPAA_R;
                    break;
                case ContentRating.Unrated:
                    mpaaRatingPictureBox.Image = Properties.Resources.MPAA_UR;
                    break;
                // TV Ratings
                case ContentRating.TV_G:
                    mpaaRatingPictureBox.Image = Properties.Resources.TV_G_dark;
                    break;
                case ContentRating.TV_PG:
                    mpaaRatingPictureBox.Image = Properties.Resources.TV_PG_dark;
                    break;
                case ContentRating.TV_Y:
                    mpaaRatingPictureBox.Image = Properties.Resources.TV_Y_dark;
                    break;
                case ContentRating.TV_Y7:
                    mpaaRatingPictureBox.Image = Properties.Resources.TV_Y7_dark;
                    break;
                case ContentRating.TV14:
                    mpaaRatingPictureBox.Image = Properties.Resources.TV_14_dark;
                    break;
                case ContentRating.TV_MA:
                    mpaaRatingPictureBox.Image = Properties.Resources.TV_MA_dark;
                    break;
                default:
                    mpaaRatingPictureBox.Image = null;
                    break;
            }

            string imagePath = string.Format("{0}{1}", item.Path, MediaItem.DefaultCoverImageFilename);
            if (File.Exists(imagePath))
                coverImagePictureBox.LoadAsync(imagePath);
            else
                coverImagePictureBox.Image = null;
        }

        private void matchTitlesAllMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MatchTitles();
        }

        private async void MatchTitles(bool onlyMissingImdbItems = false, bool onlySelectedItems = false)
        {
            toolStripProgressBar.Style = ProgressBarStyle.Continuous;
            toolStripProgressBar.Enabled = true;

            Action loadAction = GetTitleMatchSourceLoadAction();
            if (loadAction != null)
            {
                await TaskManager.Run("Load Title Match Source", loadAction);
                await TaskManager.Run("Match Titles From IMDb List", () => MatchTitlesFromImdbList(onlyMissingImdbItems, onlySelectedItems));
                InvokeMethod(() => UpdateMediaGrid());
            }
            else if (Preferences.TitleMatchingOptions.TitleMatchSource == TitleMatchingOptions.TitleMatchSources.None)
            {
                await TaskManager.Run("Match Titles From IMDb Query", () => MatchTitlesFromImdbQuery(onlyMissingImdbItems, onlySelectedItems));
                InvokeMethod(() => UpdateMediaGrid());
            }
        }

        private Action GetTitleMatchSourceLoadAction()
        {
            Action loadAction = null;
            switch (Preferences.TitleMatchingOptions.TitleMatchSource)
            {
                case TitleMatchingOptions.TitleMatchSources.MpaaRatings:
                    if (!MpaaRating.FileExists()) HandleImdbListFileNotFound();
                    else
                    {
                        MpaaRating.ProgressEvent += new EventHandler<ProgressEventArgs>(ProgressEvent);
                        loadAction = new Action(() => LoadMpaaRatings());
                    }
                    break;
                case TitleMatchingOptions.TitleMatchSources.ImdbRatings:
                    if (!ImdbRating.FileExists()) HandleImdbListFileNotFound();
                    else
                    {
                        ImdbRating.ProgressEvent += new EventHandler<ProgressEventArgs>(ProgressEvent);
                        loadAction = new Action(() => LoadImdbRatings());
                    }
                    break;
                case TitleMatchingOptions.TitleMatchSources.None:
                    return null;
                default:
                    if (DialogResult.Yes == MessageBox.Show(this, "Your IMDb Movie Title Matching Preferences are set to Full, which is not recommended and currently not supported in this version of MediaGerbil.\nIf you want to take advantage of this feature, you'll need to change the Preferences!\n\nWould you like to go to the Preferences now?", "Unable to Match Titles", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2))
                        preferencesToolStripMenuItem_Click(null, null);
                    return null;
            }
            
            return loadAction;
        }

        private bool HandleImdbListFileNotFound()
        {
            Logger.LogMessage("MPAA Ratings List not found, launching download!", LoggingLevel.Debug);
            if (DialogResult.Yes == MessageBox.Show(this, "It seems that you have not downloaded the MPAA Ratings list from the IMDb server yet. Would you like to do that now?", "MPAA Ratings list not found!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1))
            {
                updateIMDBMovieListToolStripMenuItem_Click(null, null);
                //allMediaItemsToolStripMenuItem_Click(null, null);
                return true;
            }

            return false;
        }

        private void matchTitlesOnlyItemsWithoutIMDbIDToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MatchTitles(onlyMissingImdbItems: true);
        }

        private void matchTitlesSelectedMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MatchTitles(onlySelectedItems: true);
        }

        private async void getMediaInfoAllMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> items = new List<MediaItem>(CurrentMediaItems.Count);
            foreach (MediaItem item in CurrentMediaItems)
                items.Add(item);

            Logger.LogMessage(string.Format("Getting IMDb Info for all {0} media items...", items.Count));
            await TaskManager.Run("Get IMDb Media Info", () => GetImdbMediaInfo(items));
            Logger.LogMessage("Finished getting IMDb info!");
        }

        private async void getMediaInfoSelectedMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not getting IMDb Info since there are no Media Items selected!");
                return;
            }

            Logger.LogMessage(string.Format("Getting IMDb Info for {0} selected media items...", selectedItems.Count));
            await TaskManager.Run("Get IMDb Media Info", () => GetImdbMediaInfo(selectedItems));
            Logger.LogMessage("Finished getting IMDb info!");
        }

        private async void saveCoverImageAllMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> items = new List<MediaItem>(CurrentMediaItems.Count);
            foreach (MediaItem item in CurrentMediaItems)
                items.Add(item);

            Logger.LogMessage(string.Format("Getting Cover Images for all {0} media items...", items.Count));
            await TaskManager.Run("Save Cover Image To Folder", () => SaveCoverImageToFolder(items, Preferences.CoverImageOptions));
            Logger.LogMessage("Finished getting Cover Images!");
        }

        private async void saveCoverImageAllMediaItemsWithoutCoverImageToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Logger.LogMessage("Getting Cover Images for all media items without cover images...");
            List<MediaItem> items = await TaskManager.Run<List<MediaItem>>("Get Items Without Cover Images", () => MediaCrawler.GetItemsWithoutCoverImages(CurrentMediaItems));
            await TaskManager.Run("Save Cover Image To Folder", () => SaveCoverImageToFolder(items, Preferences.CoverImageOptions));
            Logger.LogMessage("Finished getting cover images!");
        }

        private void saveCoverImageSelectedMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DownloadCoverImagesForSelectedMediaItems(Preferences.CoverImageOptions);
        }

        private async void DownloadCoverImagesForSelectedMediaItems(CoverImageOptions coverImageOptions)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not getting Cover Images since there are no Media Items selected!");
                return;
            }

            Logger.LogMessage(string.Format("Getting Cover Images for {0} selected media items...", selectedItems.Count));
            await TaskManager.Run("Save Cover Image To Folder", () => SaveCoverImageToFolder(selectedItems, coverImageOptions));
            await TaskManager.Run("Update Media Item Details View", () => UpdateMediaItemDetailsView(useSelectedMediaItem: true));
            Logger.LogMessage("Finished getting cover images!");
        }

        private void openWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Logger.LogMessage("Launching MediaGerbil website in default browser...");
            System.Diagnostics.Process.Start(MediaGerbilWebsite);
        }

        /// <summary>
        /// Gets the list of Media Items currently selected in the DataGridView.
        /// </summary>
        /// <returns>List of Media Items currently selected in the DataGridView.</returns>
        private List<MediaItem> GetSelectedMediaItems()
        {
            // There's no data to be selected, just return null
            if (dataGridView.CurrentRow == null) return null;

            List<MediaItem> selectedItems = new List<MediaItem>();

            // Check if the user selected multiple entire rows
            if (dataGridView.SelectedRows.Count > 0)
            {
                foreach (DataGridViewRow row in dataGridView.SelectedRows)
                    selectedItems.Add(row.DataBoundItem as MediaItem);
            }
            // Check if the user selected cells in multiple rows
            else if (dataGridView.SelectedCells.Count > 0)
            {
                foreach(DataGridViewCell cell in dataGridView.SelectedCells)
                {
                    MediaItem item = cell.OwningRow.DataBoundItem as MediaItem;
                    if (!selectedItems.Contains(item)) selectedItems.Add(item);
                }
            }
            else // Only one cell selected, return the item from the current row
                selectedItems.Add(dataGridView.CurrentRow.DataBoundItem as MediaItem);

            return selectedItems;
        }

        private List<MediaItem> GetMissingImdbItems()
        {
            List<MediaItem> items = new List<MediaItem>();
            foreach (MediaItem item in CurrentMediaItems)
                if (string.IsNullOrEmpty(item.ImdbId)) items.Add(item);

            return items;
        }

        private async void getMediaInfoAllMediaItemsWithoutIMDbIDToolStripMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> missingItems = GetMissingImdbItems();

            Logger.LogMessage(string.Format("Getting IMDb Info for {0} media items without IMDb ID...", missingItems.Count));
            await TaskManager.Run("Get IMDb Media Info", () => GetImdbMediaInfo(missingItems));
            Logger.LogMessage("Finished getting IMDb info!");
        }

        private void revertTitleForSelectedMediaItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not reverting any titles since there are no Media Items selected!");
                return;
            }

            Logger.LogMessage(string.Format("Reverting title on {0} selected media items...", selectedItems.Count));
            foreach (MediaItem item in selectedItems)
                item.RevertTitle();
            UpdateMediaGrid();
            Logger.LogMessage("Finished reverting titles!");
        }

        private void saveMediaListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (AllMediaItems == null || AllMediaItems.Count == 0)
            {
                Logger.LogMessage("Nothing to be saved since Media List is empty!");
            }
            else
            {
                Logger.LogMessage("Saving Media Items Cache...");
                MediaItemsCache.SaveMediaItems(AllMediaItems);
            }
        }

        private void txtTitleFilter_TextChanged(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtTitleFilter.Text))
            {
                CurrentMediaItems = AllMediaItems;
            }
            else
            {
                if (FilteredMediaItems == null) FilteredMediaItems = new AsyncBindingList<MediaItem>();
                FilteredMediaItems.Clear();

                foreach (MediaItem item in AllMediaItems)
                    if (item.Title.ToLower().IndexOf(txtTitleFilter.Text.ToLower().Trim()) != -1)
                        FilteredMediaItems.Add(item);

                CurrentMediaItems = FilteredMediaItems;
            }
            
            // Rebind DataGridView to update with new list
            dataGridView.DataSource = CurrentMediaItems;
        }

        private void chkItemsFilter_CheckedChanged(object sender, EventArgs e)
        {
            if (!chkItemsFilter.Checked) cmbItemsFilter.SelectedIndex = -1;
        }

        private void cmbItemsFilter_SelectedIndexChanged(object sender, EventArgs e)
        {
            CurrentMediaItems = AllMediaItems;
            
            switch(cmbItemsFilter.SelectedIndex)
            {
                case 0: // complete Meta Data
                    FilterByMissingMetaData();
                    break;
                case 1: // Title Matches
                    FilterByMissingTitleMatches();
                    break;
                case 2: // Cover Images
                    FilterByMissingCoverImages();
                    break;
                case 3: // a Year
                    FilterByMissingMetaData(MediaItem.MetaDataFields.Year);
                    break;
                case 4: // any MPAA Rating
                    FilterByMissingMetaData(MediaItem.MetaDataFields.ContentRating);
                    break;
                case 5: // an IMDb ID
                    FilterByMissingMetaData(MediaItem.MetaDataFields.ImdbId);
                    break;
                case 6: // a TVDb ID
                    FilterByMissingMetaData(MediaItem.MetaDataFields.TvdbId);
                    break;
            }

            // Rebind DataGridView to update with new list
            dataGridView.DataSource = CurrentMediaItems;

            // Check checkbox if filter has been selected
            if (cmbItemsFilter.SelectedIndex != -1) chkItemsFilter.Checked = true;
        }

        private void FilterByMissingMetaData(MediaItem.MetaDataFields metaDataField)
        {
            if (FilteredMediaItems == null) FilteredMediaItems = new AsyncBindingList<MediaItem>();
            FilteredMediaItems.Clear();

            IEnumerable<MediaItem> filteredItems = null;
            switch (metaDataField)
            {
                case MediaItem.MetaDataFields.Year:
                    filteredItems = AllMediaItems.Where<MediaItem>(item => item.Year == 0);
                    break;
                case MediaItem.MetaDataFields.ContentRating:
                    filteredItems = AllMediaItems.Where<MediaItem>(item => item.ContentRating == ContentRating.Unknown);
                    break;
                case MediaItem.MetaDataFields.ImdbId:
                    filteredItems = AllMediaItems.Where<MediaItem>(item => string.IsNullOrWhiteSpace(item.ImdbId));
                    break;
                case MediaItem.MetaDataFields.TvdbId:
                    filteredItems = AllMediaItems.Where<MediaItem>(item => string.IsNullOrWhiteSpace(item.TvdbId));
                    break;
            }

            foreach (MediaItem item in filteredItems)
                FilteredMediaItems.Add(item);
            CurrentMediaItems = FilteredMediaItems;
        }

        private void FilterByMissingMetaData()
        {
            if (FilteredMediaItems == null) FilteredMediaItems = new AsyncBindingList<MediaItem>();
            FilteredMediaItems.Clear();

            foreach (MediaItem item in AllMediaItems)
                if (item.IsMissingMetaData())
                    FilteredMediaItems.Add(item);

            CurrentMediaItems = FilteredMediaItems;
        }

        private async void FilterByMissingCoverImages()
        {
            if (FilteredMediaItems == null) FilteredMediaItems = new AsyncBindingList<MediaItem>();
            FilteredMediaItems.Clear();

            await TaskManager.Run("Filter By Missing Cover Images", () =>
            {
                foreach (MediaItem item in AllMediaItems)
                {
                    // HACK: Switch out the two following lines to go from cached cover image to explicit file check
                    //if (!item.HasLocalCoverImage())
                    if (!item.HasCoverImage)
                        FilteredMediaItems.Add(item);
                }
            });
            
            CurrentMediaItems = FilteredMediaItems;
            // Rebind DataGridView to update with new list
            dataGridView.DataSource = CurrentMediaItems;
        }

        private void FilterByMissingTitleMatches()
        {
            if (FilteredMediaItems == null) FilteredMediaItems = new AsyncBindingList<MediaItem>();
            FilteredMediaItems.Clear();

            Parallel.ForEach(AllMediaItems, item =>
            {
                if (item.OriginalTitle == item.Title)
                    FilteredMediaItems.Add(item);
            });

            CurrentMediaItems = FilteredMediaItems;
        }

        #region .   DataGridView Context Menu Event Handlers   .
        private void matchTitleContextMenuItem_Click(object sender, EventArgs e)
        {
            matchTitlesSelectedMediaItemsToolStripMenuItem_Click(sender, e);
        }

        private void revertTitleContextMenuItem_Click(object sender, EventArgs e)
        {
            revertTitleForSelectedMediaItemsToolStripMenuItem_Click(sender, e);
        }

        private void getIMDbMediaInfoContextMenuItem_Click(object sender, EventArgs e)
        {
            getMediaInfoSelectedMediaItemsToolStripMenuItem_Click(sender, e);
        }

        private void clearOutAllMetaDataContextMenuItem_Click(object sender, EventArgs e)
        {
            clearOutAllMetaDataForSelectedItemsToolStripMenuItem_Click(sender, e);
        }

        private async void deleteCoverImageFromFolderContextMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("No Cover Images will be deleted since no Media Items have been selected!");
                return;
            }

            if (DialogResult.Yes == MessageBox.Show(this, string.Format("Are you sure you want to delete the Cover Image files for the selected {0} items?", selectedItems.Count), "Please confirm Cover Image Deletion", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2))
            {
                Logger.LogMessage(string.Format("Deleting Cover Images for {0} selected items.", selectedItems.Count));
                await TaskManager.Run("Delete Cover Images For Items", () => MediaCrawler.DeleteCoverImagesForItems(selectedItems));
                UpdateMediaItemDetailsView(useSelectedMediaItem: true);
            }
        }

        private async void deleteBackdropImageFromFolderContextMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("No Backdrop Images will be deleted since no Media Items have been selected!");
                return;
            }

            if (DialogResult.Yes == MessageBox.Show(this, string.Format("Are you sure you want to delete the Backdrop Image files for the selected {0} items?", selectedItems.Count), "Please confirm Backdrop Image Deletion", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2))
            {
                Logger.LogMessage(string.Format("Deleting Backdrop Images for {0} selected items.", selectedItems.Count));
                await TaskManager.Run("Delete Backdrop Images For Items", () => MediaCrawler.DeleteBackdropImagesForItems(selectedItems));
                UpdateMediaItemDetailsView(useSelectedMediaItem: true);
            }
        }
        #endregion

        private void clearOutAllMetaDataForSelectedItemsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not clearing out any Meta Data since there are no Media Items selected!");
                return;
            }

            Logger.LogMessage(string.Format("Clearing out Meta Data for {0} selected items.", selectedItems.Count));
            foreach (MediaItem item in selectedItems)
                item.ClearMetaData();
            UpdateMediaGrid();
            UpdateMediaItemDetailsView(useSelectedMediaItem: true);
            Logger.LogMessage("Finished clearing out Meta Data on all selected items!");
        }

        private void PickTitleMatch(TitleMatchingOptions.TitleMatchSources titleMatchSource)
        {
            // TODO: Change this to work on all selected items!
            if (dataGridView.CurrentRow == null || dataGridView.CurrentRow.DataBoundItem == null)
            {
                Logger.LogMessage("You cannot pick a title match without selecting a title first!");
                return;
            }
            Cursor.Current = Cursors.WaitCursor;
            MediaItem item = dataGridView.CurrentRow.DataBoundItem as MediaItem;
            List<IMediaItem> results = GetTitleMatchList(titleMatchSource, item);
            Cursor.Current = Cursors.Default;
            if (results != null && results.Count > 0)
            {
                TitleMatches matchDialog = new TitleMatches(item, results);
                matchDialog.ShowDialog(this);
            }
        }

        private List<IMediaItem> GetTitleMatchList(string itemTitle, MediaType itemType)
        {
            return GetTitleMatchList(Preferences.TitleMatchingOptions.TitleMatchSource, itemTitle, itemType);
        }

        private List<IMediaItem> GetTitleMatchList(TitleMatchingOptions.TitleMatchSources titleMatchSource, MediaItem item)
        {
            return GetTitleMatchList(titleMatchSource, item.Title, item.MediaType);
        }

        private List<IMediaItem> GetTitleMatchList(TitleMatchingOptions.TitleMatchSources titleMatchSource, string itemTitle, MediaType itemType)
        {
            List<IMediaItem> results = null;

            switch (titleMatchSource)
            {
                case TitleMatchingOptions.TitleMatchSources.None:
                    results = ImdbScraper.GetImdbAdvancedTitleSearchMatches(itemTitle, itemType, Preferences.TitleCleaningOptions);
                    break;
                case TitleMatchingOptions.TitleMatchSources.MpaaRatings:
                case TitleMatchingOptions.TitleMatchSources.ImdbRatings:
                    Action loadAction = GetTitleMatchSourceLoadAction();
                    if (loadAction != null) loadAction();
                    results = GetAllTitleMatchesFromImdbList(itemTitle, (itemType == MediaType.Movie) ? MovieRatings : SeriesRatings);
                    break;
            }

            return results;
        }

        private void pickTitleMatchFromTitleSourceContextMenuItem_Click(object sender, EventArgs e)
        {
            PickTitleMatch(Preferences.TitleMatchingOptions.TitleMatchSource);
        }

        private void pickTitleMatchFromIMDbAdvancedTitleSearchContextMenuItem_Click(object sender, EventArgs e)
        {
            PickTitleMatch(TitleMatchingOptions.TitleMatchSources.None);
        }

        private void removeMediaItemFromListContextMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not removing any Media Items from list since no Media Items have been selected!");
                return;
            }

            if (DialogResult.Yes != MessageBox.Show(this, string.Format("Are you sure you want to remove the selected {0} items from the list? No files will be deleted by doing this.", selectedItems.Count), "Really remove Media Items?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2))
                return;

            Logger.LogMessage(string.Format("Removing {0} selected Media Items from list.", selectedItems.Count));
            foreach (MediaItem item in selectedItems)
                AllMediaItems.Remove(item);
            UpdateMediaGrid();
            UpdateMediaItemDetailsView(useSelectedMediaItem: true);
            Logger.LogMessage("Finished removing selected Media Items from list!");
        }

        private void showDefaultViewToolStripMenuItem_Click(object sender, EventArgs e)
        {
            HashSet<string> defaultViewColumns = new HashSet<string> { "Title", "MediaType", "Year", "ContentRating", "Rating", "Votes" };
            ToggleDataGridColumnVisibility(defaultViewColumns);
        }

        private void showTitleMatchingViewToolStripMenuItem_Click(object sender, EventArgs e)
        {
            HashSet<string> titleMatchingViewColumns = new HashSet<string> { "OriginalTitle", "Title" };
            ToggleDataGridColumnVisibility(titleMatchingViewColumns);
        }

        private void ToggleDataGridColumnVisibility(HashSet<string> visibleColumns)
        {
            foreach (DataGridViewColumn column in dataGridView.Columns)
                column.Visible = visibleColumns.Contains(column.Name);
        }

        private void showMediaTypeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dataGridView.Columns["MediaType"].Visible = showMediaTypeToolStripMenuItem.Checked;
        }

        private void showFilePathToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dataGridView.Columns["FilePath"].Visible = showFilePathToolStripMenuItem.Checked;
        }

        private void showIMDbIDToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dataGridView.Columns["ImdbId"].Visible = showIMDbIDToolStripMenuItem.Checked;
        }

        private void showTVDbIDToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dataGridView.Columns["TvdbId"].Visible = showTVDbIDToolStripMenuItem.Checked;
        }

        private void showTaskManagerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TaskManagerDialog tmd = new TaskManagerDialog();
            tmd.Show(this);
        }

        private void showTMDbIDToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dataGridView.Columns["TmdbId"].Visible = showTMDbIDToolStripMenuItem.Checked;
        }

        private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string recommendedVersion = UpdateChecker.GetRecommendedRelease();
            if (recommendedVersion == ProductVersion)
                MessageBox.Show(this, string.Format("The most current recommended release is MediaGerbil {0}, which is the version you are running right now. No Update needed!", ProductVersion), "No MediaGerbil Update available!", MessageBoxButtons.OK, MessageBoxIcon.Information);
            else if (DialogResult.Yes == MessageBox.Show(this, string.Format("The most current recommended release is MediaGerbil {0}, and you are running MediaGerbil {1}. Would you like to go the website and download the latest version? Note that if you are using the ClickOnce release, it will automatically update the next time you start MediaGerbil.", recommendedVersion, ProductVersion), "MediaGerbil Update available!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1))
                openWebsiteToolStripMenuItem_Click(sender, e);
        }

        private void saveCoverImageToFolderFromDefaultSourceContextMenuItem_Click(object sender, EventArgs e)
        {
            saveCoverImageSelectedMediaItemsToolStripMenuItem_Click(sender, e);
        }

        private void saveCoverImageToFolderFromTMDbContextMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not getting Cover Images since there are no Media Items selected!");
                return;
            }

            Logger.LogMessage(string.Format("Getting Cover Images for {0} selected media items...", selectedItems.Count));
            foreach (MediaItem item in selectedItems)
            {
                if (string.IsNullOrEmpty(item.ImdbId))
                {
                    Logger.LogMessage(string.Format("Skipping media item '{0}' because it does not have an IMDb ID.", item.Title));
                    continue;
                }
                ImagePicker picker = new ImagePicker(item, Preferences.CoverImageOptions);
                if (picker.HasMoreThanOneImageAvailable() || Preferences.CoverImageOptions.AlwaysShowImagePicker)
                    picker.ShowDialog(this);
                else
                    picker.SaveFirstCoverImageToFolder();
            }
            UpdateMediaItemDetailsView(useSelectedMediaItem: true);
            Logger.LogMessage("Finished getting Cover Images!");
        }

        private void showMediaItemDetailsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (showMediaItemDetailsToolStripMenuItem.Checked)
            {
                grpMediaItemDetails.Visible = btnShowMediaItemDetails.Visible = true;
                dataGridView.Width -= grpMediaItemDetails.Width;
            }
            else
            {
                grpMediaItemDetails.Visible = btnShowMediaItemDetails.Visible = false;
                dataGridView.Width += grpMediaItemDetails.Width;
            }
        }

        private void btnShowFilter_MouseClick(object sender, MouseEventArgs e)
        {
            int heightOffset = 62 - 15;

            if (btnShowFilter.Collapsed)
            {
                grpFilter.Height -= heightOffset;
                dataGridView.Location = new Point(dataGridView.Location.X, dataGridView.Location.Y - heightOffset);
                dataGridView.Height += heightOffset;
            }
            else
            {
                dataGridView.Height -= heightOffset;
                dataGridView.Location = new Point(dataGridView.Location.X, dataGridView.Location.Y + heightOffset);
                grpFilter.Height += heightOffset;
            }
        }

        private void btnShowMediaItemDetails_MouseClick(object sender, MouseEventArgs e)
        {
            if (btnShowMediaItemDetails.Collapsed)
            {
                pnlMediaItemDetails.Visible = false;
                coverImagePictureBox.Dock = DockStyle.Fill;
            }
            else
            {
                pnlMediaItemDetails.Visible = true;
                coverImagePictureBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
                // Set the size correctly since Windows Forms doesn't know what size to set to after coming out of DockStyle.Fill
                // and will just use the PreferredSize property. However if the window size changed in the meantime it won't line
                // up correctly anymore, so we rather set our own size.
                coverImagePictureBox.Size = new Size(coverImagePictureBox.Width, coverImagePictureBox.Parent.Height - coverImagePictureBox.Location.Y - coverImagePictureBox.Margin.Top - coverImagePictureBox.Margin.Bottom);
            }
        }

        private void saveBackdropToFolderFromTMDbContextMenuItem_Click(object sender, EventArgs e)
        {
            List<MediaItem> selectedItems = GetSelectedMediaItems();
            if (selectedItems == null)
            {
                Logger.LogMessage("Not getting Backdrop Images since there are no Media Items selected!");
                return;
            }

            Logger.LogMessage(string.Format("Getting Backdrop Images for {0} selected media items...", selectedItems.Count));
            foreach (MediaItem item in selectedItems)
            {
                if (string.IsNullOrEmpty(item.ImdbId))
                {
                    Logger.LogMessage(string.Format("Skipping media item '{0}' because it does not have an IMDb ID.", item.Title));
                    continue;
                }
                ImagePicker picker = new ImagePicker(item, Preferences.CoverImageOptions, useBackdropImages: true);
                if (picker.HasMoreThanOneImageAvailable() || Preferences.CoverImageOptions.AlwaysShowImagePicker)
                    picker.ShowDialog(this);
                else
                    picker.SaveFirstCoverImageToFolder();
            }
            UpdateMediaItemDetailsView(useSelectedMediaItem: true);
            Logger.LogMessage("Finished getting Backdrop Images!");
        }

        private void coverImagePictureBox_DoubleClick(object sender, EventArgs e)
        {
            MediaItem item = dataGridView.CurrentRow.DataBoundItem as MediaItem;
            if (item == null) return;

            // HACK: Hardcoded for JPG right now, should support other images formats such as PNG
            IEnumerable<string> images = Directory.EnumerateFiles(item.Path, "*.jpg", SearchOption.TopDirectoryOnly);
            if (images == null || images.Count() == 0) return;
            string image = string.Empty;
            if (coverImagePictureBox.Image == null) image = images.First();
            else
            {
                List<string> list = images.ToList<string>();
                int index = list.IndexOf(coverImagePictureBox.ImageLocation);
                if (index + 1 < list.Count) image = list[index + 1];
                else image = list[0];
            }
            if (!string.IsNullOrEmpty(image)) coverImagePictureBox.LoadAsync(image);
        }
    }
}
